﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data;

namespace System
{
    public static class DataExt
    {
        /// <summary>
        /// Returns copy of a table (both structure and data)
        /// And filters result for filter
        /// </summary>
        /// <param name="table"></param>
        /// <param name="filter"></param>
        /// <returns></returns>
        public static DataTable Copy(this DataTable table, string skipRowsFilter)
        {
            DataTable dt = table.Copy();
            DataRow[] rows = dt.Select(skipRowsFilter);
            foreach (DataRow r in rows)
            {
                r.Delete();
                r.AcceptChanges();
            }
            return dt;
        }
        /// <summary>
        /// Sets row column value if it is different than current value.
        /// </summary>
        /// <param name="row"></param>
        /// <param name="column"></param>
        /// <param name="value"></param>
        /// <param name="acceptChanges"></param>
        /// <returns>True if value was modified</returns>
        public static bool SetColumnValue(this DataRow row, string column, object value, bool acceptChanges = false)
        {
            if (row.CanRead() && column.IsNotNull() && row.Table.Columns.Contains(column))
            {
                if (row[column].IsTheSame(value))
                    return false;
                row[column] = value;
                if (acceptChanges)
                    row.AcceptChanges();
                return true;
            }
            return false;
        }

        /// <summary>
        /// Returns false if row is null or is deleted
        /// </summary>
        /// <param name="row"></param>
        /// <returns></returns>
        public static bool CanRead(this DataRow row)
        {
            if (row == null || row.RowState == DataRowState.Deleted)
                return false;
            return true;
        }

        #region Checking for changes
        /// <summary>
        /// Check if column value was modified.
        /// For New, Deleted and Detached rows returns true.
        /// For Modified rows checks if Oryginal value is different than Current
        /// </summary>
        /// <param name="row"></param>
        /// <param name="column"></param>
        /// <returns></returns>
        public static bool ColumnChanged(this DataRow row, string column)
        {
            if (row.RowState == DataRowState.Deleted ||
                row.RowState == DataRowState.Added ||
                row.RowState == DataRowState.Detached)
                return true;
            if (row.RowState == DataRowState.Modified)
                return !row[column, DataRowVersion.Original].IsTheSame(row[column, DataRowVersion.Current]);
            return false;
        }
        /// <summary>
        /// Checks if all columns have value equal to oryginal value
        /// </summary>
        /// <param name="row"></param>
        /// <returns>True if row is added, deleted or detached. If row is modified returns true if at least one column have 
        /// current value different than oryginal value</returns>
        public static bool RealHasChanges(this DataRow row)
        {
            if (row != null)
            {
                foreach (DataColumn col in row.Table.Columns)
                    if (row.ColumnChanged(col.ColumnName))
                        return true;
            }
            return false;
        }
        /// <summary>
        /// Gets realy changed rows
        /// </summary>
        /// <param name="dt"></param>
        /// <param name="excludeDeleted"></param>
        /// <returns></returns>
        public static DataRow[] GetRealChangedRows(this DataTable dt, bool excludeDeleted = true)
        {
            List<DataRow> list = new List<DataRow>();
            if (dt != null)
            {
                foreach (DataRow r in dt.Rows)
                {
                    if (r.RealHasChanges())
                    {
                        if (excludeDeleted)
                        {
                            if (r.RowState != DataRowState.Deleted)
                                list.Add(r);
                        }
                        else
                            list.Add(r);
                    }
                }
            }
            return list.ToArray();
        }
        /// <summary>
        /// Gets real changes
        /// </summary>
        /// <param name="dt"></param>
        /// <returns></returns>
        public static DataTable GetRealChanges(this DataTable dt)
        {
            DataTable d = dt.GetChanges();
            if (d != null)
            {
                DataRow[] rows = d.GetRealChangedRows(false);
                foreach (DataRow r in rows)
                {
                    if (!r.RealHasChanges())
                    {
                        r.Delete();
                        r.AcceptChanges();
                    }
                }
                if (d.Rows.Count == 0)
                    return null;
            }
            return d;
        }
        /// <summary>
        /// Checks if any row has real changes
        /// </summary>
        /// <param name="dt"></param>
        /// <returns></returns>
        public static bool RealHasChanges(this DataTable dt)
        {
            foreach (DataRow r in dt.Rows)
                if (r.RealHasChanges())
                    return true;
            return false;
        }
        /// <summary>
        /// Gets real changes
        /// </summary>
        /// <param name="ds"></param>
        /// <returns></returns>
        public static DataSet GetRealCahnges(this DataSet ds)
        {
            DataSet d = ds.GetChanges();
            if (d != null)
            {
                d.EnforceConstraints = false;
                bool hasChanges = false;
                foreach (DataTable t in d.Tables)
                {
                    if (!t.RealHasChanges())
                    {
                        t.Clear();
                        t.AcceptChanges();
                    }
                    else
                    {
                        hasChanges = true;
                    }
                }
                if (!hasChanges)
                    return null;
            }
            return d;
        }
        /// <summary>
        /// Checks if any row has real changes
        /// </summary>
        /// <param name="ds"></param>
        /// <returns></returns>
        public static bool RealHasChanges(this DataSet ds)
        {
            foreach (DataTable dt in ds.Tables)
            {
                foreach (DataRow r in dt.Rows)
                    if (r.RealHasChanges())
                        return true;
            }
            return false;
        }
        /// <summary>
        /// Checks if table has row for specified filter
        /// </summary>
        /// <param name="table"></param>
        /// <param name="filterExpression"></param>
        /// <returns></returns>
        public static bool RowExists(this DataTable table, string filterExpression)
        {
            return table.SelectFirstRow(filterExpression) != null;
        }
        /// <summary>
        /// Checks if row has any changes including changes in child rows
        /// </summary>
        /// <param name="row"></param>
        /// <returns></returns>
        public static bool HasChangesWithreference(this DataRow row)
        {
            if (row == null)
                return false;
            if (row.RowState != DataRowState.Unchanged && row.RowState != DataRowState.Modified)
                return true;
            if (row.RowState == DataRowState.Modified)
            {
                foreach (DataColumn col in row.Table.Columns)
                {
                    if (row.ColumnChanged(col.ColumnName))
                        return true;
                }
            }
            if (row.Table != null && row.Table.DataSet != null)
            {
                foreach (DataRelation rel in row.Table.DataSet.Relations)
                {
                    if (rel.ParentTable == row.Table)
                    {
                        if (HasChangesInChildRows(row, rel))
                            return true;
                    }
                }
            }
            return false;
        }

        public static bool HasReferencedRows(this DataRow row)
        {
            foreach (DataRelation rel in row.Table.DataSet.Relations)
            {
                DataRow[] rows = row.GetChildRows(rel);
                if (rows != null && rows.Length > 0)
                    return true;
            }
            return false;
        }
        private static bool HasChangesInChildRows(DataRow parent, DataRelation rel)
        {
            DataRow[] rows = parent.GetChildRows(rel);
            foreach (DataRow r in rows)
            {
                if (r.HasChangesWithreference())
                    return true;
            }
            return false;
        }
        #endregion

        public static DataRow[] GetAllChildRows(this DataRow row, bool skipDeleted = false)
        {
            List<DataRow> list = new List<DataRow>();
            if (row != null && row.Table != null)
            {
                DataRow[] rows = null;
                foreach (DataRelation rel in row.Table.ChildRelations)
                {
                    if (skipDeleted)
                        rows = row.GetChildRows(rel, DataRowVersion.Current);
                    else
                        rows = row.GetChildRows(rel);
                    if (rows != null)
                    {
                        list.AddRange(rows);
                    }
                }
            }
            return list.ToArray();
        }
        public static void DeleteRowWithRelation(this DataRow row)
        {
            if (row != null && row.Table != null && row.Table.DataSet != null)
            {
                foreach (DataRelation rel in row.Table.DataSet.Relations)
                {
                    if (rel.ParentTable == row.Table)
                    {
                        DeleteChildRows(row, rel);
                    }
                }
            }
            row.Delete();
        }
        
        public static void DeleteChildRows(this DataRow row)
        {
            if (row != null && row.Table != null && row.Table.DataSet != null)
            {
                foreach (DataRelation rel in row.Table.DataSet.Relations)
                {
                    if (rel.ParentTable == row.Table)
                    {
                        DataRow[] rows = row.GetChildRows(rel);
                        foreach (DataRow r in rows)
                            r.Delete();
                    }
                }
            }
        }

        private static void DeleteChildRows(DataRow parent, DataRelation rel)
        {
            DataRow[] rows = parent.GetChildRows(rel);
            if (rows != null)
            {
                foreach (DataRow r in rows)
                    r.DeleteRowWithRelation();
            }
        }

        public static DataRow SelectFirstRow(this DataTable table, string filterExpression, string sort, DataViewRowState recordStates)
        {
            if (table != null)
            {
                DataRow[] rows = table.Select(filterExpression, sort, recordStates);
                if (rows.Length > 0)
                    return rows[0];
            }
            return null;
        }
        public static DataRow SelectFirstRow(this DataTable table, string filterExpression, string sort)
        {
            return table.SelectFirstRow(filterExpression, sort, DataViewRowState.CurrentRows);
        }
        public static DataRow SelectFirstRow(this DataTable table, string filterExpression)
        {
            return table.SelectFirstRow(filterExpression, null, DataViewRowState.CurrentRows);
        }
        public static DataRow SelectFirstRow(this DataTable table)
        {
            return table.SelectFirstRow("");
        }

        public static DataTable SelectToTable(this DataTable table, string filterExpression, string sort, DataViewRowState recordStates)
        {
            if (table != null)
            {
                DataRow[] rows = table.Select(filterExpression, sort, recordStates);
                DataTable dt = table.Clone();
                foreach (DataRow r in rows)
                    dt.ImportRow(r);
                return dt;
            }
            return null;
        }
        public static DataTable SelectToTable(this DataTable table, string filterExpression, string sort)
        {
            return table.SelectToTable(filterExpression, sort, DataViewRowState.CurrentRows);
        }
        public static DataTable SelectToTable(this DataTable table, string filterExpression)
        {
            return table.SelectToTable(filterExpression, null, DataViewRowState.CurrentRows);
        }


        public static void DeleteRows(this DataTable table, string filterExpression)
        {
            if (table != null)
            {
                DataRow[] rows = table.Select(filterExpression);
                foreach (DataRow r in rows)
                    r.DeleteRowWithRelation();
            }
        }

        public static void ClearErrors(this DataTable table)
        {
            if (table != null)
                foreach (DataRow r in table.Rows)
                    r.ClearErrors();
        }

        public static bool ValidateColUnique(this DataTable table, string col, string[] groupColumns, string errorToSet, string filter, bool onlyModified)
        {
            DataRow[] allCurrentRows = table.Select(filter, null, DataViewRowState.CurrentRows);
            DataRow[] modified = table.Select(filter, null, DataViewRowState.ModifiedCurrent | DataViewRowState.Added);
            if (onlyModified)
                return ValidateColUnique(modified, allCurrentRows, col, groupColumns, errorToSet);
            else
                return ValidateColUnique(allCurrentRows, allCurrentRows, col, groupColumns, errorToSet);
        }
        public static bool ValidateColUnique(this DataTable table, string col, string[] groupColumns, string errorToSet, string filter)
        {
            return ValidateColUnique(table, col, groupColumns, errorToSet, filter, true);
        }
        public static bool ValidateColUnique(this DataTable table, string col, string[] groupColumns, string errorToSet)
        {
            return ValidateColUnique(table, col, groupColumns, errorToSet, null, true);
        }

        public static bool ValidateColUnique(this DataTable table, string col, string errorToSet, string filter, bool onlyModified)
        {
            return ValidateColUnique(table, col, null, errorToSet, filter, onlyModified);
        }
        public static bool ValidateColUnique(this DataTable table, string col, string errorToSet, string filter)
        {
            return ValidateColUnique(table, col, errorToSet, filter, true);
        }
        public static bool ValidateColUnique(this DataTable table, string col, string errorToSet)
        {
            return ValidateColUnique(table, col, errorToSet, null, true);
        }
        private static bool ValidateColUnique(DataRow[] rowsToCheck, DataRow[] allrows, string col, string errorToSet)
        {
            return ValidateColUnique(rowsToCheck, allrows, col, null, errorToSet);
        }
        private static bool ValidateColUnique(DataRow[] rowsToCheck, DataRow[] allrows, string col, string[] groupColumns, string errorToSet)
        {
            bool result = true;
            foreach (DataRow r in rowsToCheck)
            {
                foreach (DataRow r2 in allrows)
                {
                    if (r2 == r)
                        continue;
                    bool thesame = r2[col].IsTheSame(r[col]);
                    if (thesame && groupColumns != null)
                    {
                        foreach (string s in groupColumns)
                            thesame &= r2[s].IsTheSame(r[s]);
                    }
                    if (thesame)
                    {
                        r2.SetColumnError(col, errorToSet);
                        r.SetColumnError(col, errorToSet);
                        result = false;
                    }
                }
            }
            return result;
        }
        public static bool IsColumnUnique(this DataRow row, string column, string filter = null)
        {
            string f = filter;
            if (f.IsNotNull())
            {
                f = "(" + f + ") AND ";
            }
            if (row[column].IsNull())
            {
                f += column + " IS NULL";
            }
            else
                f += column + " = " + row[column].AsString().EscapeSql();
            DataRow[] rows = row.Table.Select(f, null, DataViewRowState.CurrentRows);
            if (rows.Length != 1)
                return false;
            return true;
        }
    }
}
